什么是App启动屏幕

App 启动页 (Splash),最经典的莫过于微信的启动页了

  1. 首次启动出现一个地球界面,然后才进入主界面,这是因为从桌面点击 App 图标启动应用开始,程序会显示一个启动界面等待 Activity 的创建加载完毕再进行显示
  2. 再次启动微信,发现并不会出现地球界面,而是直接进入主界面,这是因为微信并没有完全退出(在后台运行)

来看一下 Google 官方文档《Launch-Time Performance》对应用启动方式的概述:

启动方式

冷启动

冷启动指的是应用程序从头开始:系统的进程没有,直到此开始,创建了应用程序的进程。 在应用程序自设备启动以来第一次启动或系统杀死应用程序等情况下会发生冷启动。 这种类型的启动在最小化启动时间方面是最大的挑战,因为系统和应用程序比其他启动状态具有更多的工作。

cold-launch.png
cold-launch.png

热启动

与冷启动相比,热启动应用程序要简单得多,开销更低。在热启动,系统会把你活动放到前台,如果所有应用程序的活动仍驻留在内存中,那么应用程序可以避免重复对象初始化,UI的布局和渲染。
热启动显示与冷启动场景相同的屏幕行为:系统进程显示空白屏幕,直到应用程序完成呈现活动。

温启动

用户退出您的应用,但随后重新启动。该过程可能已继续运行,但应用程序必须通过调用 onCreate() 从头开始重新创建活动。系统从内存中驱逐您的应用程序,然后用户重新启动它。进程和Activity需要重新启动,但任务可以从保存的实例状态包传递到 onCreate() 中。

为什么出现白屏

冷启动白屏持续时间可能会很长,这可是个槽糕的体验,它的启动速度是由于以下引起的:
1、Application 的 onCreate 流程,对于大型的 App 来说,通常会在这里做大量的通用组件的初始化操作;
建议:很多第三方SDK都放在Application初始化,我们可以放到用到的地方才进行初始化操作。

2、Activity 的 onCreate() 流程,特别是UI的布局与渲染操作,如果布局过于复杂很可能导致严重的启动性能问题;
建议:Activity 仅初始化那些立即需要的对象,xml布局减少冗余或嵌套布局。

优化APP启动速度意义重大,启动时间过长,可能会使用户直接卸载 App 。

优化方案

优化方案主要是从主题切换的方向着手:

直接干掉

既然有这个 Activity 启动界面,那能不能直接不要这个呢,当然是可以:
定义一个 style :

<style name="SplashTheme">
<!--关闭启动窗口-->
<item name="android:windowDisablePreview">true</item>
</style>

在 manifest 修改启动页面的主题:

<activity
android:name=".SplashActivity"
android:label="@string/app_name"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>

SplashActivity 代码:

public class SplashActivity extends AppCompatActivity {

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
finish();
}
}

这样启动 App,就没有白屏,但会出现点击桌面图标而半天没有反应的现象,显然不好,据我所知 Inoreader (版本5.3) 就是采用这种方法。

Material Design

其实 Google 针对 App 闪屏,在 Material Design 规范 Launch screens,也给出了非常详细的设计定义,有两种方案:

品牌展示

patterns-launch-screens-01.png
patterns-launch-screens-01.png

屏幕提供短暂的品牌曝光,来看看如何实现的,定义一个 style :

<style name="SplashTheme">
<item name="android:windowBackground">@drawable/branded_launch_screens</item>
</style>

新建 drawable/branded_launch_screens

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
android:opacity="opaque">
<!--黑色背景颜色-->
<item android:drawable="@android:color/black" />
<!-- 产品logo-->
<item>
<bitmap
android:gravity="center"
android:src="@mipmap/empty_image01" />
</item>
<!-- 右上角的图标元素 -->
<item>
<bitmap
android:gravity="top|right"
android:src="@mipmap/github" />
</item>
<!--最下面的文字-->
<item android:bottom="50dp">
<bitmap
android:gravity="bottom"
android:src="@mipmap/ic_launcher" />
</item>
</layer-list>

其中 android:opacity=”opaque” 参数是为了防止在启动的时候出现背景的闪烁。关于 layer-list 介绍,见博客:用 layer-list 实现图片旋转叠加、错位叠加、阴影、按钮指示灯 。同样需要在 manifest 修改启动页面的主题。微信、YouTube 就是采用这种方式。

主页预加载

patterns-launch-screens-03.png
patterns-launch-screens-03.png

使用与主界面 UI 一致的占位内容,给用户感觉已经在加载中了,这里模拟了一个高度为 25dp 的状态栏和一个高度为 56dp 的标题栏,定义一个style:

<style name="SplashTheme">
<item name="android:windowBackground">@drawable/placeholder_ui</item>
</style>

新建 drawable/placeholder_ui

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
android:opacity="opaque">
<!--状态栏颜色-->
<item android:drawable="@color/colorPrimaryDark" />
<!--假装这里是个toolbar-->
<item
android:drawable="@color/colorPrimary"
android:top="25dp" />
<!--状态栏25+toolbar56=距离top81-->
<item
android:drawable="@android:color/white"
android:top="81dp" />
</layer-list>

同样需要在 manifest 修改启动页面的主题。据我所知,V2EX+ 就是采用这种方法。

注意的问题

以上的优化方案基本实现了启动页的基本功能,不过还是有一些小的问题需要注意的。

  1. 只显示一次启动页( App 没被 kill 的情况下)

    微信打开之后,按下返回键回到桌面,再打开微信,并不会再看到启动页(除非你手动清了微信的后台或者被系统 kill 了),这个是怎么实现的呢?

    其实很简单,只需要重写一下 MainActivityonBackPressed() 方法就行。

    @Override
    public void onBackPressed() {
    // super.onBackPressed(); 不要调用父类的方法
    Intent intent = new Intent(Intent.ACTION_MAIN);
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    intent.addCategory(Intent.CATEGORY_HOME);
    startActivity(intent);
    // 或者直接使用 moveTaskToBack(true);
    }

    以上就实现了和微信一样的效果,按返回键不关闭 MainActivity(好恶心的做法~~)

参考:

Android APP启动优化